//
//  ViewController.m
//  runtime
//
//  Created by zxy on 2018/4/17.
//  Copyright © 2018年 zssh. All rights reserved.
//

#import "ViewController.h"
#import <objc/runtime.h>
#import <objc/message.h>
#import "Person.h"
#import "NSObject+category.h"
#import "Cat.h"
#import "UIImage+msg.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self exchangeAction];
    [self runtimeAction2];
    [self runtimeAction3];
    [self runtimeAction4];
    [self runtimeAction5];
    [self runtimeAction6];
    [self runtimeAction7];
}

#pragma mark -runtime案例1-交换方法
-(void)exchangeAction{
    //获取Person类的二个方法
    Method m1 = class_getClassMethod([Person class], @selector(run));
    Method m2 = class_getClassMethod([Person class], @selector(study));
    
    //开始交换方法实现
    method_exchangeImplementations(m1, m2);
    
    //交换后，先打印学习，在打印跑
    [Person run];
    [Person study];
}

#pragma mark -runtime案例2-拦截系统方法
/*
需求：比如iOS6 升级 iOS7 后需要版本适配，根据不同系统使用不同样式图片（拟物化和扁平化），如何通过不去手动一个个修改每个UIImage的imageNamed：方法就可以实现为该方法中加入版本判断语句？
 
 1、为UIImage建一个分类（UIImage+Category）
 2、在分类中实现一个自定义方法，方法中写要在系统方法中加入的语句，比如版本判断
 3、分类中重写UIImage的load方法，实现方法的交换（只要能让其执行一次方法交换语句，load再合适不过了）
 */
-(void)runtimeAction2{
    /*此时如果调用系统[UIImage imageNamed:@""]方法会走我们在UIImage分类中写的方法，
     完成拦截系统方法，并实现我们的需求*/
    
}

#pragma mark -runtime案例3-在分类中设置属性，给任何一个对象设置属性
-(void)runtimeAction3{
    /*
     
     众所周知，分类中是无法设置属性的，如果在分类的声明中写@property 只能为其生成get 和 set 方法的声明，但无法生成成员变量，就是虽然点语法能调用出来，但程序执行后会crash
     
    set方法，将值value 跟对象object 关联起来（将值value 存储到对象object 中）
    参数 object：给哪个对象设置属性
    参数 key：一个属性对应一个Key，将来可以通过key取出这个存储的值，key 可以是任何类型：double、int 等，建议用char 可以节省字节
    参数 value：给属性设置的值
    参数policy：存储策略 （assign 、copy 、 retain就是strong）
    void objc_setAssociatedObject(id object , const void *key ,id value ,objc_AssociationPolicy policy)
    
    利用参数key 将对象object中存储的对应值取出来
    id objc_getAssociatedObject(id object , const void *key)
     
     
     步骤：
     1、创建一个分类，比如给任何一个对象都添加一个name属性，就是NSObject添加分类（NSObject+Category）
     2、先在.h 中@property 声明出get 和 set 方法，方便点语法调用
     3、在.m 中重写set 和 get 方法，内部利用runtime 给属性赋值和取值
     */
    UIView *v = [[UIView alloc]init];
    v.name = @"给NSObject分类添加了个name属性";
    NSLog(@"%@",v.name);
}

#pragma mark -runtime案例4-获得一个类的所有成员变量
-(void)runtimeAction4{
    /*
        最典型的用法就是一个对象在归档和解档的 encodeWithCoder和initWithCoder:方法中需要该对象所有的属性进行decodeObjectForKey: 和 encodeObject:，通过runtime我们声明中无论写多少个属性，都不需要再修改实现中的代码了。
     
     
     获得某个类的所有成员变量（outCount 会返回成员变量的总数）
     参数：
     1、哪个类
     2、放一个接收值的地址，用来存放属性的个数
     3、返回值：存放所有获取到的属性，通过下面两个方法可以调出名字和类型
     Ivar *class_copyIvarList(Class cls , unsigned int *outCount)
     
     获得成员变量的名字
     const char *ivar_getName(Ivar v)
     
     获得成员变量的类型
     const char *ivar_getTypeEndcoding(Ivar v)
     
     */
    //案例1：获取Person类中所有成员变量的名字和类型
    unsigned int outCount = 0;
    Ivar *ivars = class_copyIvarList([Cat class], &outCount);
    
    //遍历所有成员变量
    for (NSInteger i = 0; i < outCount; i++) {
        //取出对应的成员变量
        Ivar ivar = ivars[i];
        const char *name = ivar_getName(ivar);
        const char *type = ivar_getTypeEncoding(ivar);
        NSLog(@"成员变量名:%s 成员变量类型：%s",name,type);
    }
    //释放内存
    free(ivars);
    
}

#pragma mark -runtime案例5-给系统自带的方法扩展一些功能
-(void)runtimeAction5{
    /*
     1.发送消息
     方法调用的本质，就是让对象发送消息。
     objc_msgSend,只有对象才能发送消息，因此以objc开头.
     使用消息机制前提，必须导入#import <objc/message.h>
     消息机制简单使用
     // 创建person对象
     Person *p = [[Person alloc] init];
     
     // 调用对象方法
     [p eat];
     
     // 本质：让对象发送消息
     objc_msgSend(p, @selector(eat));
     
     // 调用类方法的方式：两种
     // 第一种通过类名调用
     [Person eat];
     // 第二种通过类对象调用
     [[Person class] eat];
     
     // 用类名调用类方法，底层会自动把类名转换成类对象调用
     // 本质：让类对象发送消息
     objc_msgSend([Person class], @selector(eat));
     
     消息机制原理：对象根据方法编号SEL去映射表查找对应的方法实现
     
     
     开发使用场景:系统自带的方法功能不够，给系统自带的方法扩展一些功能，并且保持原有的功能。
     方式一:继承系统的类，重写方法.
     方式二:使用runtime,交换方法.
     
     // 需求：给imageNamed方法提供功能，每次加载图片就判断下图片是否加载成功。
     // 步骤一：先搞个分类，定义一个能加载图片并且能打印的方法+ (instancetype)imageWithName:(NSString *)name;
     // 步骤二：交换imageNamed和imageWithName的实现，就能调用imageWithName，间接调用imageWithName的实现。
     
    */
    //此时调用imageNamed方法相当于调用了UIImage+msg分类的imageWithName方法，提示图片是否加载成功
    UIImage *image = [UIImage imageNamed:@""];
}

#pragma mark -runtime案例6-获取属性列表
-(void)runtimeAction6{
    unsigned int outCount = 0;
    objc_property_t *proList = class_copyPropertyList([Cat class], &outCount);
    for (NSInteger i = 0; i < outCount; i++) {
        objc_property_t pro = proList[i];
        const char *propertyName = property_getName(pro);
        NSLog(@"property-------%@",[NSString stringWithUTF8String:propertyName]);
    }
}

#pragma mark -runtime案例7-获取方法列表
-(void)runtimeAction7{
    unsigned int outCount = 0;
    Method *methodList = class_copyMethodList([Person class], &outCount);
    for (NSInteger i = 0; i < outCount; i++) {
        Method method = methodList[i];
        SEL methodName = method_getName(method);
        NSLog(@"method------%@",NSStringFromSelector(methodName));
    }
}
@end
